package cn.gem.rainbow.cfg.websocket;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * http://doc.jeecg.com/1273997  Websocket业务对接
 * http://doc.jeecg.com/1273998  WebSocket的集成
 *  https://blog.csdn.net/huiyunfei/article/details/90719351?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf
 *
 *
 * 一、websocket与http
 *
 * http协议是用在应用层的协议，他是基于tcp协议的，http协议建立链接也必须要有三次握手才能发送信息。http链接分为短链接，长链接，短链接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长链接是在一定的期限内保持链接。保持TCP连接不断开。客户端与服务器通信，必须要有客户端发起然后服务器返回结果。客户端是主动的，服务器是被动的。
 * WebSocket是HTML5中的协议， 他是为了解决客户端发起多个http请求到服务器资源浏览器必须要经过长时间的轮训问题而生的，他实现了多路复用，他是全双工通信。在webSocket协议下客服端和浏览器可以同时发送信息
 *
 * 二、HTTP的长连接与websocket的持久连接
 *
 * HTTP1.1的连接默认使用长连接（persistent connection），
 *
 * 即在一定的期限内保持链接，客户端会需要在短时间内向服务端请求大量的资源，保持TCP连接不断开。客户端与服务器通信，必须要有客户端发起然后服务器返回结果。客户端是主动的，服务器是被动的。
 *
 *   在一个TCP连接上可以传输多个Request/Response消息对，所以本质上还是Request/Response消息对，仍然会造成资源的浪费、实时性不强等问题。
 *
 * 如果不是持续连接，即短连接，那么每个资源都要建立一个新的连接，HTTP底层使用的是TCP，那么每次都要使用三次握手建立TCP连接，即每一个request对应一个response，将造成极大的资源浪费。
 *
 *   长轮询，即客户端发送一个超时时间很长的Request，服务器hold住这个连接，在有新数据到达时返回Response
 *
 * websocket的持久连接  只需建立一次Request/Response消息对，之后都是TCP连接，避免了需要多次建立Request/Response消息对而产生的冗余头部信息。
 *
 * Websocket只需要一次HTTP握手，所以说整个通讯过程是建立在一次连接/状态中，而且websocket可以实现服务端主动联系客户端，这是http做不到的
 *
 *
 *
 * 开发WebSocket的2种方法
 * 1.用原生H5中的WebSocket-----本例代码是这一种
 *https://blog.csdn.net/huiyunfei/article/details/90719351?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf
 *
 *
 * 2.用基于STOMP协议的WebSocket
 *  2.1：基于STOMP协议的WebSocket
 * 使用STOMP的好处在于，它完全就是一种消息队列模式，你可以使用生产者与消费者的思想来认识它，
 * 发送消息的是生产者，接收消息的是消费者。而消费者可以通过订阅不同的destination，
 * 来获得不同的推送消息，不需要开发人员去管理这些订阅与推送目的地之前的关系，
 * spring官网就有一个简单的spring-boot的基于STOMP协议的stomp-demo,
 * https://spring.io/guides/gs/messaging-stomp-websocket/
 * https://www.cnblogs.com/javafirst0/p/11208563.html
 * https://blog.csdn.net/szzzgyn/article/details/85599930
 */
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class MyWebSocketServer {
// //与某个客户端的连接会话，需要通过它来给客户端发送数据
    private Session session;
////concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象
    private static CopyOnWriteArraySet<MyWebSocketServer> myWebSocketServers =new CopyOnWriteArraySet<>();
    private static Map<String,Session> sessionPool = new HashMap<String,Session>();

    /**
     * @OnOpen 指定SpringBoot打开 websocket消息
     *
     * 测试js客户端打开连接
     * let c ="ws://localhost:8080/rainbow/websocket/2";
     * this.websock = new WebSocket(c)
     *
     * @param session
     * @param userId
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="userId")String userId) {
        try {
            this.session = session;
            myWebSocketServers.add(this);
            sessionPool.put(userId, session);
            log.info("连接WebSocket的userId:{},当前会话Id:{}",userId,session.getId());
            log.info("【websocket消息】有新的连接，总数为:"+ myWebSocketServers.size());
        } catch (Exception e) {
        }
    }

    /**
     * let s253 ="ws://127.0.0.1:8080/rainbow/websocket/149";
     * let w27 = new WebSocket(s253);  //打开连接
     * w27.send("qawe12"); //发送消息
     * w27.close(); //关闭连接
     *
     */
    @OnClose
    public void onClose() {
        try {
            myWebSocketServers.remove(this);
            log.info("【websocket消息】连接断开，总数为:"+ myWebSocketServers.size());
        } catch (Exception e) {
        }
    }

    /**
     *  @OnMessage 指定springBoot接收 websocket消息
     *
     * 测试js客户端发送信息
     * this.websock.send("12wqe");
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message,Session session) {
        log.info("【websocket消息】收到客户端发送的消息:{},当前会话id:{}",message,session.getId());
    }

    /**
     *
     * let s253 ="ws://127.0.0.1:8080/rainbow/websocket/149";
     * let w27 = new WebSocket(s253);  //打开连接
     * w27.send("qawe12"); //发送消息
     * w27.close(); //关闭连接
     * //获得消息事件
     *         w27.onmessage = function(msg) {
     *             console.log(msg.data);
     *             //发现消息进入    开始处理前端触发逻辑
     *         };
     * @param message
     */
    // 此为广播消息
    public static void sendAllMessage(String message) {
        log.info("【websocket消息】广播消息:"+message);
        for(MyWebSocketServer myWebSocketServer : myWebSocketServers) {
            try {
                if(myWebSocketServer.session.isOpen()) {
                    myWebSocketServer.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息
    public static void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null&&session.isOpen()) {
            try {
                log.info("【websocket消息】 单点消息:{},目标 userId:{}",message,userId);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息(多人)
    public static void sendMoreMessage(String[] userIds, String message) {
        for(String userId:userIds) {
            Session session = sessionPool.get(userId);
            if (session != null&&session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:{},目标 userId:{}",message,userId);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

}
